/**
 * 测试报告用例转换器
 *
 * Created by snowingsea on 2021/01/20.
 */
const {
  TestCaseTransformer,
  IntegrationTestCaseTransformer,
  STUB_KIND,
  VARIABLE_SCOPE_KIND,
  VARIABLE_KIND,
} = require('smart-rocket-unit');
const tests = require('./../../../tests');
const utility = require('./../../../utility');

function classCreator(type) {
  let extendClass;
  switch (type) {
    case 'integrationTest':
      extendClass = IntegrationTestCaseTransformer;
      break;
    default:
      extendClass = TestCaseTransformer;
      break;
  }

  return (class extends extendClass {
    /**
     * 构造函数
     * @param {Object} testCaseData 测试用例数据
     * @param {Object} variablesData 函数变量信息数据
     * @param {Object} typeSystemData 类型系统数据
     */
    constructor(testCaseData, variablesData, typeSystemData, locales) {
      super(testCaseData, variablesData, typeSystemData);
      this.input = []; // 测试用例输入
      this.output = []; // 测试用例输出
      this.result = {}; // 测试用例执行结果
      this.locales = locales; // 国际化配置
    }

    getStubInvokeTimeUsedPointerTargets(fields = ['%', 'params'], stubTimeValue, valueField = '@value') {
      const usedPointerTargets = [];
      if (!stubTimeValue || !fields || !valueField) {
        throw new Error('invalid: need "fields", "stubTimeValue", "valueField"');
      }
      fields.forEach((key) => {
        if (!stubTimeValue[key]) {
          return;
        }
        const fieldValues = tests.getTestCaseValues(stubTimeValue[key], valueField);
        Object.keys(fieldValues).forEach((fieldKey) => {
          const matchValues = fieldValues[fieldKey].match(/^@(.*)/);
          if (matchValues && matchValues.length === 2) {
            usedPointerTargets.push(matchValues[1]);
          }
        });
      });
      return usedPointerTargets;
    }

    reSetVariablesData(stubName, time, stubOriginalPointerTargets, scope) {
      let stubTimeValue;
      if (!stubOriginalPointerTargets) {
        return;
      }
      this.variablesData.stubs[stubName].pointerTargets = {};
      if (scope === VARIABLE_SCOPE_KIND.input
        && this.testCaseData.data
        && this.testCaseData.data.stubs
        && this.testCaseData.data.stubs[stubName]
        && this.testCaseData.data.stubs[stubName]['@value']
        && this.testCaseData.data.stubs[stubName]['@value'][time]) {
        stubTimeValue = this.testCaseData.data.stubs[stubName]['@value'][time];
      }
      if (scope === VARIABLE_SCOPE_KIND.output
        && this.testCaseData.data
        && this.testCaseData.data.output
        && this.testCaseData.data.output.stubs[stubName]
        && this.testCaseData.data.output.stubs[stubName]['@value']
        && this.testCaseData.data.output.stubs[stubName]['@value'][time]) {
        stubTimeValue = this.testCaseData.data.stubs[stubName]['@value'][time];
      }
      if (!stubTimeValue) {
        return;
      }
      const usedPointerTargets = this.getStubInvokeTimeUsedPointerTargets(['%', 'params'], stubTimeValue);
      if (usedPointerTargets.length === 0) {
        return;
      }
      Object.keys(stubOriginalPointerTargets).forEach((pointerTargetName) => {
        if (usedPointerTargets.includes(pointerTargetName)) {
          this.variablesData.stubs[stubName].pointerTargets[pointerTargetName] = { ...stubOriginalPointerTargets[pointerTargetName] };
        }
      });
    }

    /**
     * 变量信息格式化
     * @param {Object} variable 变量
     * @param {Number} depth 缩进深度
     * @param {Boolean} complete 是否输出未赋值变量
     * @param {Boolean} areaBeginning 是否为某个数据区域的首个变量
     * @return {Boolean} 是否插入数据
     */
    formatVariable(variable, depth, complete, areaBeginning = false) {
      const {
        name, typeName, data, children, scope, kind, currentStubKind, time, location, originalName, type,
      } = variable;
      const spaces = ' '.repeat(2 * depth);
      const detail = {
        name,
        originalName,
        typeName,
        type,
        kind,
        variable: `${spaces}${(typeName || '')} ${kind === VARIABLE_KIND.statics ? name : originalName || name}`,
      };
      if (kind === VARIABLE_KIND.statics) {
        detail.spaces = spaces;
        detail.location = location;
      }

      const results = scope === VARIABLE_SCOPE_KIND.output ? this.output : this.input;
      const originCount = results.length;
      let insertCount = 0;
      if (areaBeginning) {
        switch (kind) {
          case VARIABLE_KIND.object:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.object');
            break;
          case VARIABLE_KIND.global:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.global');
            break;
          case VARIABLE_KIND.param:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.variables');
            break;
          case VARIABLE_KIND.mallocVariable:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.malloc');
            break;
          case VARIABLE_KIND.return:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.returnValue');
            break;
          case VARIABLE_KIND.stub:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.stub');
            break;
          case VARIABLE_KIND.fixedAddressExpression:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.fixedAddressExpression');
            if (scope === VARIABLE_SCOPE_KIND.input) {
              insertCount += 1;
              this.input.push({
                // eslint-disable-next-line no-underscore-dangle
                category: this.locales.__('tests.fixedAddress'),
                variable: '',
                value: '',
              });
            }
            break;
          case VARIABLE_KIND.statics:
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.statics');
            break;
          case VARIABLE_KIND.fixedAddressBase:
            if (scope === VARIABLE_SCOPE_KIND.output) {
              insertCount += 1;
              this.output.push({
                // eslint-disable-next-line no-underscore-dangle
                category: this.locales.__('tests.fixedAddress'),
                variable: '',
                value: '',
                result: '',
              });
            }
            // eslint-disable-next-line no-underscore-dangle
            detail.category = this.locales.__('tests.fixedAddressBase');
            break;
          default:
            break;
        }
      } else {
        detail.category = '';
      }

      detail.value = (data && data['@value'] && data['@value'] !== '?') ? data['@value'] : '';
      if (utility.isObjectType(detail.value) || utility.isArrayType(detail.value)) {
        detail.value = '';
      }
      // 填充output边界值
      if (data && data['@tolerance']) {
        detail.tolerance = data['@tolerance'];
      }

      // 桩
      if (kind === VARIABLE_KIND.stub) {
        if (currentStubKind === STUB_KIND.code) {
          // 代码打桩
          detail.value = (data && data.body) ? data.body : '';
          if (detail.value) {
            detail.values = detail.value.split('\n').map((line) => ({ line }));
          }
        } else if (time || time === 0) {
          // 数值打桩
          // eslint-disable-next-line no-underscore-dangle
          detail.variable = `${spaces}${this.locales.__n('tests.%d time', (time + 1) % 100, { n: time + 1 })}${this.locales.__('tests.invoked')}`;
        }
      }

      if (scope === VARIABLE_SCOPE_KIND.output) {
        detail.result = (data && data['@result']) ? data['@result'] : '';
        const existValue = detail.value !== '';
        const existResult = detail.result !== '';
        if (this.testCaseData.result
          && (
            [
              tests.resultKind.succeed,
              tests.resultKind.noResult,
              tests.resultKind.nonStrictSucceed,
              tests.resultKind.failed,
            ].indexOf(this.testCaseData.result.kind) >= 0
          )
        ) {
          if (existValue && !existResult) {
            detail.result = detail.value;
          } else if (!existValue && existResult) {
            // detail.value = detail.result;
          }
        }
      }
      // 类构造函数
      if (detail.value && variable.constructors) {
        variable.constructors.forEach((ctor) => {
          if (ctor['@mangled'] === detail.value) {
            detail.value = ctor['@nameparams'];
          }
        });
      }

      results.push(detail);
      insertCount += 1;

      if (children) {
        children.forEach((child) => {
          this.formatVariable(child, depth + 1, complete);
        });
      }
      if (detail.result) {
        return true;
      }
      if (kind === VARIABLE_KIND.stub && !time && time !== 0) {
        // 输出检查项中桩函数名那一栏，显示预期调用次数以及实际调用次数
        if (data['@countExpected']) {
          detail.value = `调用${data['@countExpected']}次`;
          detail.result = `调用${data['@count'] || 0}次`;
        }
      }
      if (!complete && !detail.value && originCount + insertCount === results.length) {
        while (insertCount > 0) {
          insertCount -= 1;
          results.pop();
        }
        return false;
      }
      return true;
    }

    getAreas() {
      return [
        this.objectData,
        this.paramVariablesData,
        this.globalVariablesData,
        this.staticsData,
        this.stubsData,
        this.stubsOutputData,
        this.mallocVariablesData,
        this.fixedAddressExpressionsData,
        this.fixedAddressBasesData,
        this.returnValueData,
        this.objectOutputData,
        this.globalOutputData,
        this.staticsOutputData,
        this.mallocOutputData,
        this.fixedAddressBasesOutputData,
        // this.exceptionsOutputData,
      ];
    }

    /**
     * 对测试用例数据进行转换与填充
     * @param {Boolean} complete 是否输出未赋值变量
     * @param {Object} locales 国际化配置
     */
    transform(complete = false) {
      this.description = this.testCaseData.description;
      super.transform();
      this.buildTestCaseResult(this.locales);
      if (!this.description) {
        delete this.description;
      }

      if (this.prelude) {
        this.prelude = this.prelude.split('\n').map((line) => ({ line }));
      }

      const areas = this.getAreas();
      areas.forEach((area) => {
        let areaBeginning = true;
        area.forEach((variable) => {
          if (this.formatVariable(variable, 0, complete, areaBeginning)) {
            areaBeginning = false;
          }
        });
      });
    }

    /**
     * 构建桩函数数据
     * @param {String} stubName 桩函数名
     */
    buildStubFields(stubName) {
      if (!this.testCaseData.data.stubs[stubName]) {
        this.testCaseData.data.stubs[stubName] = {};
      }
      const stubOriginal = this.testCaseData.data.stubs[stubName];
      const stub = this.variablesData.stubs[stubName];
      const returnTypeName = stub['%'] ? stub['%']['@type'] : 'void';
      const name = TestCaseTransformer.formatStubFieldName(returnTypeName, stub['@name'] || stubName, stub.params);

      // 代码打桩
      if (stubOriginal.kind === STUB_KIND.code) {
        this.stubsData.push({
          kind: VARIABLE_KIND.stub,
          name,
          stubName,
          typeName: returnTypeName,
          data: stubOriginal,
          currentStubKind: STUB_KIND.code,
        });
        return;
      }
      let stubPointerTargets;
      if (this.variablesData.stubs
        && this.variablesData.stubs[stubName]
        && this.variablesData.stubs[stubName].pointerTargets) {
        stubPointerTargets = { ...this.variablesData.stubs[stubName].pointerTargets };
        this.variablesData.stubs[stubName].pointerTargets = {};
      }
      // 数值打桩
      const times = [];
      if (!stub.times) {
        stub.times = [];
      }
      if (stubOriginal['@value']) {
        Object.keys(stubOriginal['@value']).forEach((time) => {
          times.push(parseInt(time, 10));
        });
        const result = {
          fieldPath: `stubs.${stubName}`,
          kind: VARIABLE_KIND.stub,
          name,
          stubName,
          typeName: returnTypeName,
          times,
          currentStubKind: STUB_KIND.value,
          children: [],
          data: stubOriginal,
        };
        this.stubsData.push(result);
        Object.keys(stubOriginal['@value']).forEach((time) => {
          const childResult = {
            fieldPath: `stubs.${stubName}`,
            kind: VARIABLE_KIND.stub,
            name,
            stubName,
            typeName: returnTypeName,
            time: parseInt(time, 10),
            children: [],
            data: stubOriginal,
          };
          result.children.push(childResult);
          this.reSetVariablesData(stubName, time, stubPointerTargets, VARIABLE_SCOPE_KIND.input);
          this.addStubOutputFields(stubName, childResult.time, childResult.children);
        });
      }
      // 桩函数输出检查项
      if (this.variablesData.output
        && this.variablesData.output.stubs
        && this.variablesData.output.stubs[stubName]
        && this.testCaseData.data
        && this.testCaseData.data.output
        && this.testCaseData.data.output.stubs
        && this.testCaseData.data.output.stubs[stubName]) {
        const stubOutputOriginal = this.testCaseData.data.output.stubs[stubName];
        if (stubOutputOriginal['@value'] || stubOutputOriginal['@countExpected']) {
          const outputResult = {
            fieldPath: `output.stubs.${stubName}`,
            kind: VARIABLE_KIND.stub,
            name,
            stubName,
            typeName: returnTypeName,
            times,
            currentStubKind: STUB_KIND.value,
            children: [],
            scope: VARIABLE_SCOPE_KIND.output,
            data: stubOutputOriginal,
          };
          this.stubsOutputData.push(outputResult);
          if (stubOutputOriginal['@value']) {
            Object.keys(stubOutputOriginal['@value']).forEach((time) => {
              const childResult = {
                fieldPath: `output.stubs.${stubName}`,
                kind: VARIABLE_KIND.stub,
                name,
                stubName,
                typeName: returnTypeName,
                time: parseInt(time, 10),
                children: [],
                scope: VARIABLE_SCOPE_KIND.output,
                data: stubOutputOriginal,
              };
              outputResult.children.push(childResult);
              this.reSetVariablesData(stubName, time, stubPointerTargets, VARIABLE_SCOPE_KIND.output);
              this.addStubOutputFields(stubName, childResult.time, childResult.children, VARIABLE_SCOPE_KIND.output);
            });
          }
        }
      }
    }

    // eslint-disable-next-line no-unused-vars
    buildStubsOutput(stubName) {
      // to nothing just override
    }

    /**
     * 获取测试用例结果信息
     * @param {Object} locales 国际化配置
     */
    buildTestCaseResult(locales) {
      const testCase = this.testCaseData;
      const result = {};
      const runtimeErrorExist = [];
      switch (testCase.result.kind) {
        case tests.resultKind.unknown:
          break;
        case tests.resultKind.succeed:
          result.resultKind = 'succeed';
          break;
        case tests.resultKind.nonStrictSucceed:
          result.resultKind = 'nonStrictSucceed';
          break;
        case tests.resultKind.failed:
          result.resultKind = 'failed';
          break;
        case tests.resultKind.noResult:
          result.resultKind = 'noResult';
          break;
        case tests.resultKind.runtimeError:
        case tests.resultKind.timeout:
          result.resultKind = 'codeDefect';
          runtimeErrorExist.push('');
          break;
        default:
          result.resultKind = 'exception';
          runtimeErrorExist.push('');
          break;
      }
      switch (testCase.result.kind) {
        case tests.resultKind.runtimeError:
          result.message = {
            kind: locales.__('tests.runtimeError'), // eslint-disable-line no-underscore-dangle
            suggestion: locales.__('tests.runtimeErrorSuggestion'), // eslint-disable-line no-underscore-dangle
          };
          break;
        case tests.resultKind.timeout:
          result.message = {
            kind: locales.__('tests.timeoutError'), // eslint-disable-line no-underscore-dangle
            suggestion: locales.__('tests.timeoutErrorSuggestion'), // eslint-disable-line no-underscore-dangle
          };
          break;
        case tests.resultKind.systemError:
          result.message = {
            kind: locales.__('tests.systemError'), // eslint-disable-line no-underscore-dangle
            suggestion: locales.__('tests.systemErrorSuggestion'), // eslint-disable-line no-underscore-dangle
          };
          break;
        case tests.resultKind.instrumentError:
          result.message = {
            kind: locales.__('tests.instrumentError'), // eslint-disable-line no-underscore-dangle
            suggestion: locales.__('tests.instrumentErrorSuggestion'), // eslint-disable-line no-underscore-dangle
          };
          break;
        case tests.resultKind.compileError:
          result.message = {
            kind: locales.__('tests.compileError'), // eslint-disable-line no-underscore-dangle
            suggestion: locales.__('tests.compileErrorSuggestion'), // eslint-disable-line no-underscore-dangle
          };
          break;
        default:
          break;
      }
      result.runtimeErrorExist = runtimeErrorExist;
      if (testCase.log) {
        result.location = testCase.log.location;
      }
      this.result = result;
    }
  });
}

module.exports = classCreator;
